package com.gitee.slowcreator.http.executor.okhttp;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.gitee.slowcreator.http.config.HttpContextManager;
import com.gitee.slowcreator.http.config.OkHttpConfig;
import com.gitee.slowcreator.http.constant.HttpContentType;
import com.gitee.slowcreator.http.constant.HttpExecutorConstant;
import com.gitee.slowcreator.http.enums.RequestType;
import com.gitee.slowcreator.http.exception.HttpRequestException;
import com.gitee.slowcreator.http.executor.HttpExecutor;
import com.gitee.slowcreator.http.file.download.DownloadResult;
import com.gitee.slowcreator.http.file.download.FileDownloadFactory;
import com.gitee.slowcreator.http.file.download.IFileDownload;
import com.gitee.slowcreator.http.params.HttpClientParams;
import com.gitee.slowcreator.http.params.ScMultipart;
import com.gitee.slowcreator.http.params.ScReturn;
import com.gitee.slowcreator.http.params.ScUnionParams;
import com.gitee.slowcreator.http.util.JacksonUtil;
import com.gitee.slowcreator.http.util.UrlUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * okhttp的http执行器
 * 其实这个代码和hutool的http执行器很大程度是一样 但是request、response不一样 不好统一配置
 * 后面有时间再调整吧
 *
 * @author zxw
 * @date 2023/3/18 上午8:14
 */
@Slf4j
public class OkhttpHttpExecutor implements HttpExecutor {

    @Override
    public ScReturn doHttpRequest(HttpClientParams httpClient, ScUnionParams scUnionParams) {

        // 最后执行会使用 ScUnionParams 的 httpUrl 参数 如果为空 使用注解的url赋值过去
        if (StrUtil.isBlank(scUnionParams.getHttpUrl())) {
            scUnionParams.setHttpUrl(httpClient.getUrl());
        }

        RequestBody requestBody = this.getRequestBody(httpClient, scUnionParams);
        RequestType requestType = Optional.ofNullable(scUnionParams.getRequestType()).orElse(httpClient.getRequestType());
        Request.Builder requestBuilder = getHttpMethod(requestType, requestBody);
        requestBuilder = requestBuilder.url(
                UrlUtil.rebuildUrl(scUnionParams.getHttpUrl(), scUnionParams.getHttpParams())
        );

        if (CollectionUtil.isNotEmpty(scUnionParams.getHttpHeader())) {
            // 添加 header 参数
            for (Map.Entry<String, String> entry : scUnionParams.getHttpHeader().entrySet()) {

                requestBuilder.header(entry.getKey(), URLEncodeUtil.encode(entry.getValue()));
            }
        }

        int timeout = httpClient.getTimeout();
        // 超时时间小于-1 设置为-1 即无超时时间
        timeout = Math.max(timeout, -1);
        // okhttp无超时时间为0
        timeout = timeout == -1 ? 0 : timeout;
        // 设置超时时间
        HttpContextManager.get().setRequestTimeout(timeout);
        if (httpClient.isAsync()) {
            OkHttpConfig.getOkHttpClient().newCall(requestBuilder.build()).enqueue(new Callback() {

                @Override
                public void onFailure(@NotNull Call call, @NotNull IOException e) {
                    // scUnionParams 可能存在文件很大 不能直接打印 转成json打印 文件数据字段设置了 @JsonIgnore
                    // 虽然阿里巴巴开发手册说了 日志不能实体类转json 可能会报错 但是这个类有公开的无参构造函数 还是转json好一点
                    log.error("请求异常，HttpClientParams:{},ScUnionParams:{},异常信息:",
                            httpClient, JacksonUtil.toJsonString(scUnionParams), e);
                }

                @Override
                public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {

                    // 因为会有文件处理相关 所以返回值还是要处理
                    ScReturn scReturn = requestResult(httpClient, scUnionParams, response);
                    // 因为涉及文件请求相关 并且是异步 返回值就不输出了
                    scReturn.setData(null);
                    // 打印一下执行结果
                    log.info("异步请求 HttpClientParams:{},scUnionParams:{},处理结束:{}",
                            httpClient, JacksonUtil.toJsonString(scUnionParams), scReturn);
                }
            });
            // 异步请求 直接返回成功
            ScReturn scReturn = new ScReturn();
            scReturn.setSuccess(true);
            return scReturn;
        } else {
            try {
                Response execute = OkHttpConfig.getOkHttpClient().newCall(requestBuilder.build()).execute();
                return requestResult(httpClient, scUnionParams, execute);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    @SneakyThrows
    private ScReturn requestResult(HttpClientParams httpClient,
                                   ScUnionParams scUnionParams,
                                   Response execute) {

        ScReturn scReturn = new ScReturn();

        // 是否为下载请求
        boolean download = httpClient.isDownload();
        boolean newDownload = httpClient.getHttpDownloadParams().isDownload();
        int code = execute.code();
        boolean ok = code >= 200 && code < 300;
        scReturn.setSuccess(ok);
        if (download || newDownload) {
            // 下载
            DownloadResult downloadResult = downloadFileHandle(httpClient, execute);
            boolean requestSuccess = downloadResult.isRequestSuccess();
            scReturn.setDownloadResult(downloadResult);
            if (!requestSuccess) {
                scReturn.setSuccess(false);
            }
        } else {
            // 非下载请求获取返回数据
            ResponseBody body = execute.body();
            byte[] bytes;
            if (body == null) {
                log.info("请求:{}, 返回数据为空！", httpClient);
                bytes = new byte[0];
            } else {
                bytes = body.bytes();
            }

            scReturn.setData(bytes);
        }
        // 设置返回头部信息 如果需要
        getReturnHeader(scUnionParams, execute, scReturn);
        // 获取返回 Content-Type
        getContentType(execute, scReturn);

        return scReturn;
    }

    private RequestBody getRequestBody(HttpClientParams httpClient, ScUnionParams scUnionParams) {

        Map<String, Object> httpBody = scUnionParams.getHttpBody();

        boolean multipart = httpClient.isMultipart();
        if (multipart) {
            // 文件类型额外处理
            return multipartExtraProcessing(httpClient, scUnionParams);
        }

        httpBody = Optional.ofNullable(httpBody).orElse(new HashMap<>());
        if (StrUtil.contains(httpClient.getContentType(), "application/json")) {
            // json 请求
            return RequestBody.create(
                    JacksonUtil.toJsonString(httpBody),
                    MediaType.parse(StrUtil.format(httpClient.getContentType(), httpClient.getCharset())));
        } else {
            FormBody.Builder fb = new FormBody.Builder();
            if (CollectionUtil.isNotEmpty(httpBody)) {
                for (Map.Entry<String, Object> entry : httpBody.entrySet()) {
                    Object value = entry.getValue();
                    value = value == null ? "" : value;
                    fb.add(entry.getKey(), value.toString());
                }
            }

            return fb.build();
        }
    }

    /**
     * 获取返回 Content-Type
     *
     * @param execute
     * @param scReturn
     */
    private static void getContentType(Response execute, ScReturn scReturn) {
        String contentType = execute.header("Content-Type");
        if (StrUtil.isBlank(contentType)) {
            contentType = execute.header("content-type");
        }
        scReturn.setContentType(contentType);
    }

    /**
     * 获取返回头部信息
     *
     * @param scUnionParams
     * @param execute
     * @param scReturn
     */
    private static void getReturnHeader(ScUnionParams scUnionParams,
                                        Response execute,
                                        ScReturn scReturn) {

        Set<String> returnHeader = scUnionParams.getReturnHeader();
        Map<String, String> retH = new HashMap<>();
        scReturn.setReturnHeader(retH);
        if (CollectionUtil.isNotEmpty(returnHeader)) {
            for (String header : returnHeader) {
                String h = execute.header(header);
                retH.put(header, h);
            }
        }
    }

    private DownloadResult downloadFileHandle(HttpClientParams httpClient, Response execute) {

        boolean download = httpClient.isDownload();
        HttpClientParams.HttpDownloadParams httpDownloadParams = httpClient.getHttpDownloadParams();
        IFileDownload<Request, Response> handle;
        DownloadResult downloadResult = null;
        if (download) {
            // 说明是使用的废弃方法
            downloadFile(httpClient, execute);
        } else {
            handle = FileDownloadFactory.getHandle(httpDownloadParams.getFileDownloadHandle());
            if (httpDownloadParams.isUseByte()) {
                downloadResult = handle.downloadByte(null, execute);
            } else {
                downloadResult = handle.downloadFile(null, execute);
            }
        }

        return downloadResult;
    }

    /**
     * 下载文件
     *
     * @param httpClient
     * @param execute
     */
    @Deprecated
    @SneakyThrows
    private static void downloadFile(HttpClientParams httpClient,
                                     Response execute) {

        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        boolean downloadLocal = httpClient.isDownloadLocal();
        String downloadLocalFilename = httpClient.getDownloadLocalFilename();
        ResponseBody body = execute.body();
        if (body == null) {
            log.info("请求:{}, 返回数据为空！", httpClient);
            return;
        }
        byte[] bytes = body.bytes();
        if (downloadLocal) {
            // 写入文件到本地
            FileUtil.writeBytes(bytes, downloadLocalFilename);
        } else {
            // 写入数据到 输出流
            IoUtil.write(os, false, bytes);
        }
    }

    /**
     * 文件类型额外处理
     *
     * @param httpClient    http请求参数
     * @param scUnionParams 请求参数
     */
    private static RequestBody multipartExtraProcessing(HttpClientParams httpClient,
                                                        ScUnionParams scUnionParams) {

        ScMultipart scMultipartParams = scUnionParams.getScMultipart();
        byte[] multipartData = scMultipartParams.getData();
        String fileName = scMultipartParams.getFileName();
        if (ArrayUtil.isEmpty(multipartData)) {
            throw new HttpRequestException("multipart 请求，文件不能为空！");
        }
        if (StrUtil.isBlank(fileName)) {
            throw new HttpRequestException("multipart 请求，文件名不能为空！");
        }

        String fileFormName = scMultipartParams.getFileFormName();
        fileFormName = StrUtil.isBlank(fileFormName) ? "file" : fileFormName;
        RequestBody fileBody = RequestBody.create(
                multipartData,
                MediaType.parse(StrUtil.format(HttpContentType.FORM_URLENCODED, httpClient.getCharset()))
        );

        return new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart(fileFormName, fileName, fileBody)
                .build();
    }

    /**
     * 获取http请求方法
     *
     * @param requestType
     * @return
     */
    private static Request.Builder getHttpMethod(RequestType requestType, RequestBody requestBody) {

        if (requestType == null) {
            throw new IllegalArgumentException("请求方式不能为空！");
        }

        Request.Builder method;
        switch (requestType) {
            case GET:
                // get 没有body参数
                method = new Request.Builder().get();
                break;
            case POST:
                method = new Request.Builder().post(requestBody);
                break;
            case PUT:
                method = new Request.Builder().put(requestBody);
                break;
            case DELETE:
                method = new Request.Builder().delete(requestBody);
                break;
            default:
                throw new HttpRequestException("不支持的请求方式！");
        }

        return method;
    }

    @Override
    public String executorType() {

        return HttpExecutorConstant.EXECUTOR_TYPE_OKHTTP;
    }
}
